OOP

2018-11-11

Some things I've come across while on my JS Algorithms and Data Structures kick on FCC:

Although I've come across the following before I haven't put some into much use sooo let the jot down commence!

Data Structure and OOP thingsss:

Let's talk bracket notation

Passing a string within the brackets, WhateverHere['followers'] vs. WhateverHere[followers].

The former allows variable to be passed and evaluated as a property name. Man, I forget quotations on properties more than I'd like to admit.

Iterate through keys of an object using for...in:

let users = {
  Alan: {
    age: 27,
    online: false
  },
  Jeff: {
    age: 32,
    online: true
  },
  Sarah: {
    age: 48,
    online: false
  },
  Ryan: {
    age: 19,
    online: true
  }
};

function countOnline(obj) {
  let count = 0;
  for(let user in users) {
    console.log(users[user].online);
    if(users[user].online == true) {
      count++;
    }
  }
  return count;
}

console.log(countOnline(users));

Generate array of object keys using Object.keys:

let users = {
  Alan: {
    age: 27,
    online: false
  },
  Jeff: {
    age: 32,
    online: true
  },
  Sarah: {
    age: 48,
    online: false
  },
  Ryan: {
    age: 19,
    online: true
  }
};

function getArrayOfUsers(obj) {
  return Object.keys;
}

console.log(getArrayOfUsers(users));

On that OOP tip, objects jazz:

The following created a basic Dog object, where 'lab' creates a new instance of Dog.

function Dog(name, color) {
  this.name = name,
  this.color = color,
  this.numLegs = 4
}

let lab = new Dog('rodi', 'brown);

Preventing duplicates in instances:

function Dog(name) {
  this.name = name;
}

// prevents numerous var duplicates in instances created, below prototype is obj shared among all instances
Dog.prototype.numLegs = 4;

let beagle = new Dog('Snoopy');

console.log(beagle.numLegs);

own prop vs. prototype prop:

function Dog(name) {
  this.name = name; // own prop
}

Dog.prototype.numLegs = 4; // prototype prop

let beagle = new Dog('Snoopy');

let ownProps = [];
let prototypeProps = [];

for(let prop in beagle) {
  if(beagle.hasOwnProperty(prop)) {
    ownProps.push(prop);
  }
  else {
    prototypeProps.push(prop);
  }
}

efficiency; add props:

function Dog(name) {
  this.name = name;
}

// add props to prototype at once instead of individually
Dog.prototype = {
  numLegs: 2,
  eat: function() {
    console.log('Nom');
  },
  describe: function() {
    console.log(`Bark! My name is ${this.name}`)
  }
}

side-effect on manually setting up prototype on new obj: ^ it erases constructor

function Dog(name) {
  this.name(name);
}

Dog.prototype = {
  constructor: Dog, // the fix: define constructor prop
  numLegs: 2,
  eat: function() {
    console.log('nom nom nom');
  },
  describe: function() {
    console.log(`My name is ${this.name}`);
  }
}

let beagle = new Dog();

console.log(beagle.constructor);

where obj prototype comes from:

function Dog(name) {
  this.name = name;
}

let beagle = new Dog('Snoopy);

// obj inherits its prototype from constructor function that created it
Dog.prototype.isPrototypeOf(beagle); // returns true

supertypes: dog is supertype of beagle (subtype) Object is supertype of Dog AND beagle

.hasOwnProperty is defined in Object.prototype, able to be accessed across 'prototype chain'

Object is supertype for all objects in JS, any object can use .hasOwnProperty method

function Dog(name) {
  this.name = name;
}

let beagle = new Dog('Snoopy');

Dog.prototype.isPrototypeOf(beagle); // => true
Object.prototype.isPrototypeOf(Dog.prototype); // => true

keeping things DRY: the eat method was moved to Animal supertype instead of being repeated twice in Cat and Bear

function Cat(name) {
  this.name = name;
}

Cat.prototype = {
  constructor: Cat,
}

function Bear(name) {
  this.name = name;
}

Bear.prototype = {
  constructor: Bear,
}

function Animal() {}

Animal.prototype = {
  constructor: Animal,
  eat: function() {
    console.log('nom nom nom');
  }
}

alternative to new in creating instances:

let animal = Object.create(Animal.prototype)

Object.create(obj) creates a new object and sets obj as new object's prototype. Rememebering that prototype is like a 'formula' for creating an object-- setting prototype of animal to be Animal's prototype-- you are giving animal instance the same 'formula' as any instance of Animal.

animal.eat(); // prints 'nom nom nom'
animal instanceof Animal; // => true

a bit more fleshed out example:

function Animal() {}

Animal.prototype = {
  constructor: Animal,
  eat: function() {
    console.log('nom nom nom');
  }
}

let duck = Object.create(Animal.prototype);
let beagle = Object.create(Animal.prototype);

duck.eat(); // should print 'nom nom nom'
beagle.eat();

setting prototype of subtype(child):

function Animal() {}

Animal.prototype = {
  constructor: Animal,
  eat: function() {
    console.log('nom nom nom');
  }
}

function Dog() {}

/**
  sets prototype of subtype (child), Dog, to be instance of Animal
  prototype is like 'formula' for creating object; Dog now includes all 'steps/ingredients' from Animal;
  beagle inherits Animal props including eat method
**/
Dog.prototype = Object.create(Animal.prototype);

let beagle = new Dog();
beagle.eat(); // should print 'nom nom nom'

When object inherits its prototype from another object, it also inherits supertype's constructor prop.

However, beagle and all instances of Dog should show that they were constructed by Dog, not Animal.

The fix: manually set Dog's constructor prop to Dog object, same for duck

function Animal() {}
function Bird() {}
function Dog() {}

Bird.prototype = Object.create(Animal.prototype);
Dog.prototype = Object.create(Animal.prototype);

let duck = new Bird();
Bird.prototype.constructor = Bird;
duck.constructor

let beagle - new Dog();
Dog.prototype.constructor = Dog;
dog.constructor

adding methods after inheritance:

function Animal() {}
Animal.prototype.eat = function() { console.log('nom nom nom'); }

function Dog() {}
Dog.prototype = Object.create(Animal.prototype);
Dog.prototype.constructor = Dog;

Dog.prototype.bark = function() {
  console.log('woof!');
}

let beagle = new Dog();

beagle.eat(); // should print 'nom nom nom'
beagle.bark(); // should print 'woof!'

above adds behavior that is unique to Dog obj

that prototype chain

When you have an instance let greyhound = new Dog(); and call greyhound.eat(), JavaScript looks for the method on greyhound prototypechain.

  1. greyhound => is eat() defined here? No.
  2. Dog => is eat() defined here? Yes. Carry out and stop looking.
  3. Animal => eat() is also defined, but JS stopped the search before reaching this level.
  4. Object => JavaScript stopped searching before getting to this level.

Something like:

function Bird() {}

Bird.prototype.fly = function() { return 'flyyyyyying'; };

function Penguin() {}
// inherits all methods from Bird
Penguin.prototype = Object.create(Bird.prototype);
Penguin.prototype.constructor = Penguin;

// Penguin.fly() overrides Bird.fly()
Penguin.prototype.fly = function() { return 'Alas, I am flightess.'; };

let penguin = new Penguin();
console.log(penguin.fly());

when inheritance won't do; unrelated objects; mixins

A bird and boat can both glide, yet are not both animals...

let bird = {
  name: 'Daffy',
  numLegs: 2
}

let boat = {
  name: 'Pegasus',
  type: 'cruiser'
}

let glideMixin = function(obj) {
  obj.glide = function() {
    return 'glidin'
  }
}

glideMixin(bird);
glideMixin(boat);

The advantage of the module pattern, behaviors can be packaged into single object able to be reused by other parts of code.

motionModule.glideMixin(duck);
duck.glide();

using closure to protect props within obj from being modified externally

function Bird() {
  let weight = 15; // private prop

  // publicaly accessible method that any bird obj can use
  this.getWeight = function() {
    return weight;
  }
 }

IIFE (Immediately Invoked Function Expression)

function has no name, anonymous, not stored in a variable-- the parenthesis at the end causes it to be executed right away

(function() {
  console.log('I get executed right away!');
})();

grouping mixins into module using IIFE

advantage: all behaviors packaged into single obj

from this:

let isCuteMixin = function(obj) {
  obj.isCute = function() {
    return true;
  }
}

let singMixin = function(obj) {
  obj.sing = function() {
    console.log('singin my own tune')
  }
}

to this:

let funModule = (function() {
  return {
    isCuteMixin: function(obj) {
      obj.isCute = function() {
        return true;
      }
    },
    singMixin: function(obj) {
      obj.sing = function() {
        console.log('singin my own tune')
      }
    }
  }
})();

← go back